/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.service.support;

import net.sf.cglib.beans.BeanMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.transaction.annotation.Transactional;
import org.vacoor.nothing.common.reflect.GenericBeanSupport;
import org.vacoor.nothing.common.util.StringUtils;
import org.vacoor.nothing.web.dao.CrudDao;
import org.vacoor.nothing.web.dao.support.mbt.MyBatisMapper;
import org.vacoor.nothing.web.domain.Filters;
import org.vacoor.nothing.web.domain.Page;
import org.vacoor.nothing.web.domain.Pageable;
import org.vacoor.nothing.web.domain.Sort;
import org.vacoor.nothing.web.domain.support.SimplePage;
import org.vacoor.nothing.web.service.CrudService;
import org.vacoor.nothing.web.service.ServiceException;
import org.vacoor.nothing.web.spring.inject.GenericAutowired;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.vacoor.nothing.web.dao.CrudDao.*;

/**
 * CRUD Service 实现
 *
 * @author vacoor
 */
@Transactional
@SuppressWarnings("unused")
public abstract class CrudServiceImpl<E, ID extends Serializable> extends GenericBeanSupport<E> implements CrudService<E, ID> {
    private static final Logger LOG = LoggerFactory.getLogger(CrudServiceImpl.class);
    private static final String PARAMS_MAP_KEY = "______PARAMS_MAP_KEY_____";
    private static final String PROP_DELIMITER = ".";
    protected final Class<E> entityClass;
    @GenericAutowired
    private CrudDao<E, ID> crudDao;

    public void setCrudDao(CrudDao<E, ID> crudDao) {
        this.crudDao = crudDao;
    }

    protected CrudDao<E, ID> getCrudDao() {
        return this.crudDao;
    }

    /* **********************************************************
     *                    Retrieve Method
     * ******************************************************** */

    /**
     * 根据 ID 检索一个实体
     *
     * @param id 唯一标识
     */
    @Override
    public E find(ID id) {
        LOG.debug("find, id: {}", id);
        return crudDao.find(id);
        /*
        Map<String, Object> paramsMap = createParamsMap();
        paramsMap.put("id", id);
        return crudDao.findOne(paramsMap);
        */
    }

    /**
     * 根据给定属性及值检索一个唯一实体
     *
     * @param property 属性名称
     * @param value    属性值
     */
    @Override
    public E findOne(String property, Object value) {
        Map<String, Object> params = createParamsMap();
        params.put(property, value);
        return findOne(params);
    }

    /**
     * 根据给定的条件检索一个唯一实体
     *
     * @param params 条件参数
     * @return 符合条件的唯一实体
     */
    @Override
    public E findOne(Map<String, Object> params) {
        params = ensureParamsMap(params);
        LOG.debug("findOne, params: {}", params);

        params = needSplitCascadeProperty() ? resolveCascade(params) : params;
        return crudDao.findOne(params);
    }

    /**
     * 获取符合给定属性和值的实体集合
     *
     * @param property 属性
     * @param value    属性值
     * @return 符合条件的所有实体
     */
    @Override
    public List<E> findMany(String property, Object value) {
        Map<String, Object> params = createParamsMap();
        params.put(property, value);
        return findMany(params);
    }

    /**
     * 获取符合给定的过滤器和排序的实体集合
     *
     * @param filters 过滤器
     * @param sort    排序
     * @return 过滤后实体
     */
    @Override
    public List<E> findMany(Filters filters, Sort sort) {
        Map<String, Object> paramsMap = createParamsMap();
        paramsMap = resolveFiltersToParamsMap(filters, paramsMap);
        paramsMap = resolveSortToParamsMap(sort, paramsMap);

        return findMany(paramsMap);
    }

    /**
     * 返回给定分页参数下符合给定过滤器的实体集合
     *
     * @param filters  过滤器
     * @param pageable 分页参数
     */
    @Override
    public List<E> findMany(Filters filters, Pageable pageable) {
        Map<String, Object> paramsMap = createParamsMap();
        paramsMap = resolveFiltersToParamsMap(filters, paramsMap);
        paramsMap = resolvePageableToParamsMap(pageable, paramsMap);

        return findMany(paramsMap);
    }

    @Override
    public List<E> findMany(Map<String, Object> params) {
        params = ensureParamsMap(params);
        LOG.debug("findMany, params: {}", params);

        params = needSplitCascadeProperty() ? resolveCascade(params) : params;
        return crudDao.findMany(params);
    }

    /**
     * 获取所有的实体集合
     */
    @Override
    public List<E> findAll() {
        return findMany(createParamsMap());
    }

    /**
     * 分页查询并且统计
     *
     * @param filters  过滤器
     * @param pageable 分页参数
     * @return 分页结果
     */
    @Override
    public Page<E> findPage(Filters filters, Pageable pageable) {
        int count = count(filters);
        // 如果数量为 0 就没必要查询了
        List<E> data = count > 0 ? findMany(filters, pageable) : Collections.<E>emptyList();
        return new SimplePage<E>(pageable, data, count);
    }

    /**
     * 分页查询并统计
     *
     * @param paramsMap 过滤参数
     * @param pageable  分页参数
     * @return 分页结果
     */
    public Page<E> findPage(Map<String, Object> paramsMap, Pageable pageable) {
        paramsMap = ensureParamsMap(paramsMap);
        resolvePageableToParamsMap(pageable, paramsMap);

        int count = count(paramsMap);
        List<E> data = count > 0 ? findMany(paramsMap) : Collections.<E>emptyList();

        return new SimplePage<E>(pageable, data, count);
    }

    /**
     * 统计所有结果集
     *
     * @return 结果集数量
     */
    @Override
    public int count() {
        return count(createParamsMap());
    }

    /**
     * 统计符合条件的结果集
     *
     * @param property 属性
     * @param value    属性值
     * @return 结果集数量
     */
    @Override
    public int count(String property, Object value) {
        Map<String, Object> paramsMap = createParamsMap();
        paramsMap.put(property, value);

        return count(paramsMap);
    }

    /**
     * 使用给定的过滤器统计
     *
     * @param filters 统计过滤器
     * @return 结果集数量
     */
    @Override
    public int count(Filters filters) {
        Map<String, Object> paramsMap = createParamsMap();
        paramsMap = resolveFiltersToParamsMap(filters, paramsMap);
        return count(paramsMap);
    }

    /**
     * 统计符合条件的结果集
     *
     * @param params 过滤参数
     * @return 结果集数量
     */
    @Override
    public int count(Map<String, Object> params) {
        params = ensureParamsMap(params);
        params = needSplitCascadeProperty() ? resolveCascade(params) : params;
        return crudDao.count(params);
    }

    /* ***************************************************************
     *                       CREATE METHOD
     * ************************************************************* */

    @Override
    public void save(E e) {
        LOG.debug("save: {}", e);
        crudDao.save(e);
    }

    /* ***************************************************************
     *                        UPDATE METHOD
     * ************************************************************* */

    @Override
    public void update(E e) {
        LOG.debug("update: {}", e);
        crudDao.update(e);
        // update(e, new String[0]);
    }

    /**
     * 更新给定实体中忽略属性列表意外的属性
     *
     * @param e
     * @param ignoreProperties
     */
    private void update(E e, String... ignoreProperties) {
        BeanMap beanMap = BeanMap.create(e);
        Map<String, Object> partial = new HashMap<String, Object>(beanMap);
        for (String prop : ignoreProperties) {
            partial.remove(prop);
        }
        update((ID) beanMap.get("id"), partial);
    }

    /**
     * 部分更新
     *
     * @param id
     * @param partial
     */
    private void update(ID id, Map<String, Object> partial) {
        E e = find(id);
        if (null == e) {
            throw new ServiceException("not exists entity for id = " + id);
        }
        partial.remove("id");
        // ignore list remove
        partial = new HashMap<String, Object>(partial);
        // for hibernate
        BeanMap beanMap = BeanMap.create(e);
        beanMap.putAll(partial);
        // for mybatis
        // update(partial);
    }

    /* ****************************************************************
     *                        DELETE METHOD
     * ************************************************************** */

    @Override
    public void delete(E e) {
        crudDao.delete(e);
    }

    @Override
    public void delete(ID id) {
        LOG.debug("delete, id: {}", id);

        E e = find(id);
        if (e == null) {
            throw new ServiceException("data is not exists");
        }

        delete(e);
    }

    /**
     * 删除给定标识符对应的实体集合
     *
     * @param ids 标识符集合
     */
    @Override
    public void delete(ID[] ids) {
        for (ID id : ids) {
            delete(id);
        }
    }

    /**
     * 删除符合条件的实体
     *
     * @param property 属性名
     * @param value    属性值
     */
    @Override
    public void delete(String property, Object value) {
        Map<String, Object> paramsMap = createParamsMap();
        paramsMap.put(property, value);

        delete(paramsMap);
    }

    /**
     * 删除符合条件的实体
     *
     * @param params 参数Map
     */
    @Override
    public void delete(Map<String, Object> params) {
        List<E> many = findMany(params);
        if (many == null || many.size() < 1) {
            throw new ServiceException("data is not exists");
        }

        for (E one : many) {
            delete(one);
        }
    }

    /* **************************************************************
     *                       HELPER METHOD
     * ************************************************************ */

    /**
     * 设置分页参数到给定的参数Map
     *
     * @param pageable  分页参数
     * @param paramsMap 要填充的参数Map
     */
    protected Map<String, Object> resolvePageableToParamsMap(Pageable pageable, Map<String, Object> paramsMap) {
        if (null != pageable) {
            paramsMap.put(OFFSET_PROP, pageable.getOffset());           // 偏移量
            paramsMap.put(MAX_RESULTS_PROP, pageable.getPageSize());    // 最大结果数量

            paramsMap = resolveSortToParamsMap(pageable.getSort(), paramsMap);
        }
        return paramsMap;
    }

    /* **************************************
     *  约定:
     *  1. 每个实体的参数map 中key 为属性名, value 为过滤值
     *  2. 当参数key 对应另外一个实体时, 值又是一个参数 map
     *  3. 每个参数 map 中默认包含以下特殊 key
     *  _filters: Filters 实例, 动态过滤参数
     *  _sort:  Sort 实例, 动态排序参数
     *  _offset: 分页参数 - 偏移量
     *  _maxResults: 分页参数 - 最大结果数
     *
     *  对于 mybatis 为了支撑以上约定, 额外约定:
     *  表别名应该与属性名对应
     * ************************************ */


    /**
     * 将一个包含 属性导航 key 的 Map 转换到一个 导航Map
     * eg: {a.b.c: 123, a.id: uuid } --> { a:{ id: uuid, b:{ c:123 } } }
     * 主要用来处理传递给 mybatis 的参数 map
     *
     * @param paramsMap 未解析的参数Map
     * @return 已解析参数Map
     */
    @SuppressWarnings("unchecked")
    protected Map<String, Object> resolveCascade(Map<String, ?> paramsMap) {
        Map<String, Object> cascadeParamsMap = new HashMap<String, Object>();
        for (Map.Entry<String, ?> entry : paramsMap.entrySet()) {
            Map<String, Object> ref = cascadeParamsMap;
            String prop = entry.getKey();
            Object value = entry.getValue();
            int index;
            while (needSplitCascadeProperty() && -1 < (index = prop.indexOf(PROP_DELIMITER))) {
                String path = prop.substring(0, index);
                prop = prop.substring(index + 1);
                Map<String, Object> subMap = (Map<String, Object>) ref.get(path);
                if (null == subMap) {
                    subMap = new HashMap<String, Object>();
                    ref.put(path, subMap);
                }
                ref = subMap;
            }
            ref.put(prop, value);
        }
        return cascadeParamsMap;
    }

    /**
     * 将 包含 属性导航 属性 的Filters 转换到约定的 paramsMap
     * eg:
     * a.b.c.d eq 10 -> {a: {b: {c: { _filters: filters(d eq 10) } } } }
     *
     * @param filters 过滤器
     */
    @SuppressWarnings("unchecked")
    protected Map<String, Object> resolveFiltersToParamsMap(Filters filters, Map<String, Object> paramsMap) {
        if (null == filters) return paramsMap;
        if (null == paramsMap) paramsMap = createParamsMap();

        final boolean needSplit = needSplitCascadeProperty();
        for (Filters.Filter filter : filters) {
            Map<String, Object> ref = paramsMap;
            String prop = filter.getProperty();
            Filters.Operator op = filter.getOperator();
            Object values = filter.getValues();

            int index;
            // 如果需要拆分
            while (needSplit && -1 < (index = prop.indexOf(PROP_DELIMITER))) {
                String path = prop.substring(0, index);
                prop = prop.substring(index + 1);
                Map<String, Object> subMap = (Map<String, Object>) ref.get(path);
                if (null == subMap) {
                    subMap = new HashMap<String, Object>();
                    ref.put(path, subMap);
                }
                ref = subMap;
            }

            Object o = ref.get(FILTERS_PROP);
            if (null == o) {
                ref.put(FILTERS_PROP, o = Filters.create());
            }

            if (o instanceof Filters) {
                Filters orig = (Filters) o;
                orig.add(transformDynamicPropertyIfNecessary(prop), op, (Object[]) values); // 数组和可变参数问题, 因此需要强转为数组
            }
        }
        return paramsMap;
    }

    /**
     * 将 包含 属性导航 属性 的Filters 转换到约定的 paramsMap
     * eg:
     * a.b.c.d asc -> {a: {b: {c: { _sort: Sort(d asc) } } } }
     *
     * @param sort 过滤器
     */
    @SuppressWarnings("unchecked")
    protected Map<String, Object> resolveSortToParamsMap(Sort sort, Map<String, Object> paramsMap) {
        if (null == sort) return paramsMap;
        if (null == paramsMap) paramsMap = createParamsMap();

        final boolean needSplitProp = needSplitCascadeProperty();
        for (Sort.Order order : sort) {
            Map<String, Object> ref = paramsMap;
            String prop = order.getProperty();
            Sort.Direction dir = order.getDirection();
            int index;
            while (needSplitProp && -1 < (index = prop.indexOf(PROP_DELIMITER))) {
                String path = prop.substring(0, index);
                prop = prop.substring(index + 1);
                Map<String, Object> subMap = (Map<String, Object>) ref.get(path);
                if (null == subMap) {
                    subMap = new HashMap<String, Object>();
                    ref.put(path, subMap);
                }
                ref = subMap;
            }
            Object o = ref.get(SORT_PROP);
            if (null == o) {
                ref.put(SORT_PROP, o = Sort.create());
            }
            if (o instanceof Sort) {
                Sort orig = (Sort) o;
                orig.add(dir, transformDynamicPropertyIfNecessary(prop));
            }
        }
        return paramsMap;
    }

    /* ***********************************
     *
     * ********************************** */

    /**
     * 如果需要则转换动态条件属性名
     */
    protected String transformDynamicPropertyIfNecessary(String property) {
        if (!needTransformDynamicProperty() || FILTERS_PROP.equals(property) || SORT_PROP.equals(property) || OFFSET_PROP.equals(property) || MAX_RESULTS_PROP.equals(property)) {
            return property;
        }
        String prop = StringUtils.camelCaseToUnderscore(property, true);
        LOG.debug("transform property {} --> {}", property, prop);
        return prop;
    }

    /**
     * 是否需要拆分级联属性
     */
    protected boolean needSplitCascadeProperty() {
        CrudDao<E, ID> dao = getCrudDao();
        // 标注或超类/接口标注 MyBatisMapper
        return null != dao && null != AnnotationUtils.findAnnotation(dao.getClass(), MyBatisMapper.class);
    }

    protected boolean needTransformDynamicProperty() {
        CrudDao<E, ID> dao = getCrudDao();
        // 标注或超类/接口标注 MyBatisMapper
        return null != dao && null != AnnotationUtils.findAnnotation(dao.getClass(), MyBatisMapper.class);
        /*
        CrudDao<E, ID> dao = getCrudDao();
        return null != dao && dao.getClass().isAnnotationPresent(MyBatisMapper.class);
        */
        // TODO 暂时返回true, 以便兼容之前没有标注 MyBatisMapper 的版本
        // return true;
    }

    /**
     * 创建一个参数 Map
     * 尽可能使用该方法来创建参数 Map 以便统一处理某些操作(eg: 进行数据权限过滤等)
     */
    protected ParamsMap createParamsMap() {
        ParamsMap paramsMap = new ParamsMap();
        return paramsMap;
    }

    /**
     * 保证给定的 paramsMap 是参数 Map (主要是为了保证数据权限过滤等条件已添加)
     */
    private ParamsMap ensureParamsMap(Map<String, Object> paramsMap) {
        ParamsMap _paramsMap;
        if (null == paramsMap) {
            _paramsMap = createParamsMap();
        } else if (ParamsMap.class.isInstance(paramsMap)) {
            _paramsMap = (ParamsMap) paramsMap;
        } else {
            // 如果不是参数 map
            _paramsMap = createParamsMap();
            _paramsMap.putAll(paramsMap);
        }
        return _paramsMap;
    }

    protected class ParamsMap extends HashMap<String, Object> {
    }


    @SuppressWarnings("unchecked")
    public CrudServiceImpl() {
        entityClass = getActualTypeArgument();
    }
}
